﻿using System;
using System.Collections.Generic;
using System.Threading;
using MLSharp;
using MLSharp.Classification;
using MLSharp.Evaluation;
using MLSharp.Filters;

namespace MLSharp.ConsoleRunner
{
	/// <summary>
	/// A harness for running one or more classifiers
	/// against a data set via k-Fold Cross Validation.
	/// </summary>
	public class CrossValidationHarness : IHarness
	{
		#region Private Static Fields

		/// <summary>
		/// The logger for the experiment.
		/// </summary>
		private static readonly log4net.ILog mLogger =
			log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region Private Fields

		/// <summary>
		/// The classifiers.
		/// </summary>
		private readonly IClassifierFactory[] mClassifierFactories;

		/// <summary>
		/// The number of correct classifications for each classifier.
		/// </summary>
		private int[] mCorrect;

		#endregion

		#region Public Properties

		/// <summary>
		/// Flag that indicates whether or not the harness should attempt
		/// to parallelize classifier execution.
		/// </summary>
		public bool RunInParallel { get; set; }

		/// <summary>
		/// Flag that indicates whether or not to print a grid of confusion matrices after
		/// classifying the data.
		/// </summary>
		public string ConfusionGridFilename { get; set; }

		/// <summary>
		/// The collection of result writers to write results to.
		/// </summary>
		public IList<IResultWriter> ResultWriters { get; private set; }

		/// <summary>
		/// The filters to apply to the dataset.
		/// </summary>
		public IList<IDataSetFilter> Filters { get; private set; }

		#endregion

		#region Public Constructors

		/// <summary>
		/// Creates the cross-validator harness.
		/// </summary>
		/// <param name="classifierFactories">The factories to use.</param>
		public CrossValidationHarness(params IClassifierFactory[] classifierFactories)
		{
			if (classifierFactories == null ||
				classifierFactories.Length == 0)
			{
				throw new InvalidOperationException("You have to specify at least one classifier factory.");
			}

            mClassifierFactories = classifierFactories;

			ResultWriters = new List<IResultWriter>();

			Filters = new List<IDataSetFilter>();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Runs the classifiers on the specified partition.
		/// </summary>
		/// <param name="partition"></param>
		private void ProcessPartition(Partition partition)
		{
			ClassificationResult[][] results = new ClassificationResult[mClassifierFactories.Length][];

			for (int i = 0; i < mClassifierFactories.Length; i++)
			{
				//Train The classifier...
				if (mLogger.IsDebugEnabled)
				{
					mLogger.DebugFormat("Training {0}...", mClassifierFactories[i].GetType().Name);
				}

				IClassifier classifier = mClassifierFactories[i].BuildClassifier(partition.Training);

				//Now classify the validation set
				if (mLogger.IsDebugEnabled)
				{
					mLogger.Debug("Classifying validation set...");
				}

				results[i] = classifier.Classify(partition.Validation);

				//And count the number of correct classifications
				int correct = partition.Validation.CountCorrectClassifications(results[i]);

				Interlocked.Add(ref mCorrect[i], correct);
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Runs the harness.
		/// </summary>
		/// <param name="dataSet">The data set to use.</param>
		public void Run(IDataSet dataSet)
		{
			////Notify the result writers about the classifiers
			//foreach (IResultWriter writer in ResultWriters)
			//{
			//    foreach (IClassifierFactory factory in mClassifierFactories)
			//    {
			//        writer.AddClassifier(factory.GetType().Name);
			//    }
			//}

			////Partition the data.
			//Partition[] partitions = KFold.GetPartitions(dataSet, 10);

			////Initialize the counter for number of correct classifications
			mCorrect = new int[mClassifierFactories.Length];

			//if (RunInParallel)
			//{
			//    Partition.ParallelForEach(partitions, p => ProcessPartition(p));
			//    //Parallel.ForEach(partitions, p => ProcessPartition(p, outputWriter));
			//}
			//else
			//{
			//    //For each partition
			//    foreach (Partition p in partitions)
			//    {
			//        ProcessPartition(p);
			//    }
			//}

			////TODO: Push this out to a result writer?
			//if (mLogger.IsInfoEnabled)
			//{
			//    mLogger.Info("Execution complete, accuracies for each classifier: ");

			//    for (int i=0; i < mCorrect.Length; i++)
			//    {
			//        mLogger.InfoFormat("Classifier {0}: {1}/{2} [{3}]",
			//                           i + 1, mCorrect[i], dataSet.Instances.Count, mCorrect[i]/(double) dataSet.Instances.Count);
			//    }
			//}

			//TODO: This needs to support multiple classifiers
			throw new NotImplementedException();
		}

		#endregion
	}
}
